home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2010 April
/
PCWorld0410.iso
/
pluginy Firefox
/
1843
/
1843.xpi
/
components
/
firebug-service.js
< prev
next >
Wrap
Text File
|
2010-01-15
|
84KB
|
2,746 lines
/* See license.txt for terms of usage */
// Debug lines are marked with at column 120
// Use variable name "fileName" for href returned by JSD, file:/ not same as DOM
// Use variable name "url" for normalizedURL, file:/// comparable to DOM
// Convert from fileName to URL with normalizeURL
// We probably don't need denormalizeURL since we don't send .fileName back to JSD
// ************************************************************************************************
// Constants
const CLASS_ID = Components.ID("{a380e9c0-cb39-11da-a94d-0800200c9a66}");
const CLASS_NAME = "Firebug Service";
const CONTRACT_ID = "@joehewitt.com/firebug;1";
const Cc = Components.classes;
const Ci = Components.interfaces;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
const PrefService = Cc["@mozilla.org/preferences-service;1"];
const DebuggerService = Cc["@mozilla.org/js/jsd/debugger-service;1"];
const ConsoleService = Cc["@mozilla.org/consoleservice;1"];
const Timer = Cc["@mozilla.org/timer;1"];
const ObserverServiceFactory = Cc["@mozilla.org/observer-service;1"];
const jsdIDebuggerService = Ci.jsdIDebuggerService;
const jsdIScript = Ci.jsdIScript;
const jsdIStackFrame = Ci.jsdIStackFrame;
const jsdICallHook = Ci.jsdICallHook;
const jsdIExecutionHook = Ci.jsdIExecutionHook;
const jsdIErrorHook = Ci.jsdIErrorHook;
const jsdIFilter = Components.interfaces.jsdIFilter;
const nsISupports = Ci.nsISupports;
const nsIPrefBranch = Ci.nsIPrefBranch;
const nsIPrefBranch2 = Ci.nsIPrefBranch2;
const nsIComponentRegistrar = Ci.nsIComponentRegistrar;
const nsIFactory = Ci.nsIFactory;
const nsIConsoleService = Ci.nsIConsoleService;
const nsITimer = Ci.nsITimer;
const nsITimerCallback = Ci.nsITimerCallback;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
const NS_ERROR_NO_INTERFACE = Components.results.NS_ERROR_NO_INTERFACE;
const NS_ERROR_NOT_IMPLEMENTED = Components.results.NS_ERROR_NOT_IMPLEMENTED;
const NS_ERROR_NO_AGGREGATION = Components.results.NS_ERROR_NO_AGGREGATION;
const PCMAP_SOURCETEXT = jsdIScript.PCMAP_SOURCETEXT;
const PCMAP_PRETTYPRINT = jsdIScript.PCMAP_PRETTYPRINT;
const COLLECT_PROFILE_DATA = jsdIDebuggerService.COLLECT_PROFILE_DATA;
const DISABLE_OBJECT_TRACE = jsdIDebuggerService.DISABLE_OBJECT_TRACE;
const HIDE_DISABLED_FRAMES = jsdIDebuggerService.HIDE_DISABLED_FRAMES;
const DEBUG_WHEN_SET = jsdIDebuggerService.DEBUG_WHEN_SET;
const MASK_TOP_FRAME_ONLY = jsdIDebuggerService.MASK_TOP_FRAME_ONLY;
const TYPE_FUNCTION_CALL = jsdICallHook.TYPE_FUNCTION_CALL;
const TYPE_FUNCTION_RETURN = jsdICallHook.TYPE_FUNCTION_RETURN;
const TYPE_TOPLEVEL_START = jsdICallHook.TYPE_TOPLEVEL_START;
const TYPE_TOPLEVEL_END = jsdICallHook.TYPE_TOPLEVEL_END;
const RETURN_CONTINUE = jsdIExecutionHook.RETURN_CONTINUE;
const RETURN_VALUE = jsdIExecutionHook.RETURN_RET_WITH_VAL;
const RETURN_THROW_WITH_VAL = jsdIExecutionHook.RETURN_THROW_WITH_VAL;
const RETURN_CONTINUE_THROW = jsdIExecutionHook.RETURN_CONTINUE_THROW;
const NS_OS_TEMP_DIR = "TmpD"
const STEP_OVER = 1;
const STEP_INTO = 2;
const STEP_OUT = 3;
const STEP_SUSPEND = 4;
const TYPE_ONE_SHOT = nsITimer.TYPE_ONE_SHOT;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
const BP_NORMAL = 1;
const BP_MONITOR = 2;
const BP_UNTIL = 4;
const BP_ONRELOAD = 8; // XXXjjb: This is a mark for the UI to test
const BP_ERROR = 16;
const BP_TRACE = 32; // BP used to initiate traceCalls
const LEVEL_TOP = 1;
const LEVEL_EVAL = 2;
const LEVEL_EVENT = 3;
const COMPONENTS_FILTERS = [
new RegExp("^(file:/.*/)extensions/%7B[\\da-fA-F]{8}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{12}%7D/components/.*\\.js$"),
new RegExp("^(file:/.*/)extensions/firebug@software\\.joehewitt\\.com/components/.*\\.js$"),
new RegExp("^(file:/.*/extensions/)\\w+@mozilla\\.org/components/.*\\.js$"),
new RegExp("^(file:/.*/components/)ns[A-Z].*\\.js$"),
new RegExp("^(file:/.*/components/)firebug-[^\\.]*\\.js$"),
new RegExp("^(file:/.*/Contents/MacOS/extensions/.*/components/).*\\.js$"),
new RegExp("^(file:/.*/modules/).*\\.jsm$"),
];
const reDBG = /DBG_(.*)/;
const reXUL = /\.xul$|\.xml$/;
const reTooMuchRecursion = /too\smuch\srecursion/;
// ************************************************************************************************
// Globals
var jsd, fbs, prefs;
var consoleService;
var observerService;
var contextCount = 0;
var urlFilters = [
'chrome://',
'XStringBundle',
'x-jsd:ppbuffer?type=function', // internal script for pretty printing
];
var clients = [];
var debuggers = [];
var netDebuggers = [];
var scriptListeners = [];
var stepMode = 0;
var stepFrame;
var stepFrameLineId;
var stepStayOnDebuggr; // if set, the debuggr we want to stay within
var stepFrameCount;
var hookFrameCount = 0;
var haltDebugger = null;
var breakpoints = {};
var breakpointCount = 0;
var disabledCount = 0;
var monitorCount = 0;
var conditionCount = 0;
var runningUntil = null;
var errorBreakpoints = [];
var profileCount = 0;
var profileStart;
var enabledDebugger = false;
var reportNextError = false;
var breakOnNextError = false;
var errorInfo = null;
var timer = Timer.createInstance(nsITimer);
var waitingForTimer = false;
var FBTrace = null;
// ************************************************************************************************
function FirebugService()
{
FBTrace = Cc["@joehewitt.com/firebug-trace-service;1"]
.getService(Ci.nsISupports).wrappedJSObject.getTracer("extensions.firebug");
fbs = this;
this.wrappedJSObject = this;
this.timeStamp = new Date(); /* explore */
this.breakpoints = breakpoints; // so chromebug can see it /* explore */
this.onDebugRequests = 0; // the number of times we called onError but did not call onDebug
fbs._lastErrorDebuggr = null;
this.profiling = false;
this.pauseDepth = 0;
prefs = PrefService.getService(nsIPrefBranch2);
fbs.prefDomain = "extensions.firebug.service."
prefs.addObserver(fbs.prefDomain, fbs, false);
observerService = ObserverServiceFactory.getService(Ci.nsIObserverService);
observerService.addObserver(QuitApplicationGrantedObserver, "quit-application-granted", false);
observerService.addObserver(QuitApplicationRequestedObserver, "quit-application-requested", false);
observerService.addObserver(QuitApplicationObserver, "quit-application", false);
this.scriptsFilter = "all";
// XXXjj For some reason the command line will not function if we allow chromebug to see it.?
this.alwayFilterURLsStarting = ["chrome://chromebug", "x-jsd:ppbuffer", "chrome://firebug/content/commandLine.js"]; // TODO allow override
this.onEvalScriptCreated.kind = "eval";
this.onTopLevelScriptCreated.kind = "top-level";
this.onEventScriptCreated.kind = "event";
this.onXScriptCreatedByTag = {}; // fbs functions by script tag
this.nestedScriptStack = Components.classes["@mozilla.org/array;1"]
.createInstance(Components.interfaces.nsIMutableArray); // scripts contained in leveledScript that have not been drained
}
FirebugService.prototype =
{
osOut: function(str)
{
if (!this.outChannel)
{
try
{
var appShellService = Components.classes["@mozilla.org/appshell/appShellService;1"].
getService(Components.interfaces.nsIAppShellService);
this.hiddenWindow = appShellService.hiddenDOMWindow;
this.outChannel = "hidden";
}
catch(exc)
{
consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
consoleService.logStringMessage("Using consoleService because nsIAppShellService.hiddenDOMWindow not available "+exc);
this.outChannel = "service";
}
}
if (this.outChannel === "hidden") // apparently can't call via JS function
this.hiddenWindow.dump(str);
else
consoleService.logStringMessage(str);
},
shutdown: function() // call disableDebugger first
{
timer = null;
if (!jsd)
return;
try
{
do
{
var depth = jsd.exitNestedEventLoop();
}
while(depth > 0);
}
catch (exc)
{
// Seems to be the normal path...FBTrace.sysout("FirebugService, attempt to exitNestedEventLoop fails "+exc);
}
try
{
prefs.removeObserver(fbs.prefDomain, fbs);
}
catch (exc)
{
FBTrace.sysout("fbs prefs.removeObserver fails "+exc, exc);
}
try
{
observerService.removeObserver(QuitApplicationGrantedObserver, "quit-application-granted");
observerService.removeObserver(QuitApplicationRequestedObserver, "quit-application-requested");
observerService.removeObserver(QuitApplicationObserver, "quit-application");
}
catch (exc)
{
FBTrace.sysout("fbs quit-application-observers removeObserver fails "+exc, exc);
}
jsd = null;
if (!jsd)
FBTrace.sysout("*********************** SHUTDOWN JSD NULL ");
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// nsISupports
QueryInterface: function(iid)
{
if (!iid.equals(nsISupports))
throw NS_ERROR_NO_INTERFACE;
return this;
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// nsIObserver
observe: function(subject, topic, data)
{
fbs.obeyPrefs();
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
get lastErrorWindow()
{
var win = this._lastErrorWindow;
this._lastErrorWindow = null; // Release to avoid leaks
return win;
},
registerClient: function(client) // clients are essentially XUL windows
{
clients.push(client);
return clients.length;
},
unregisterClient: function(client)
{
for (var i = 0; i < clients.length; ++i)
{
if (clients[i] == client)
{
clients.splice(i, 1);
break;
}
}
},
registerDebugger: function(debuggrWrapper) // first one in will be last one called. Returns state enabledDebugger
{
var debuggr = debuggrWrapper.wrappedJSObject;
if (debuggr)
{
debuggers.push(debuggr);
if (debuggers.length == 1)
this.enableDebugger();
}
else
throw "firebug-service debuggers must have wrappedJSObject";
try {
if (debuggr.suspendActivity)
netDebuggers.push(debuggr);
} catch(exc) {
}
try {
if (debuggr.onScriptCreated)
scriptListeners.push(debuggr);
} catch(exc) {
}
return debuggers.length; // 1.3.1 return to allow Debugger to check progress
},
unregisterDebugger: function(debuggrWrapper)
{
var debuggr = debuggrWrapper.wrappedJSObject;
for (var i = 0; i < debuggers.length; ++i)
{
if (debuggers[i] == debuggr)
{
debuggers.splice(i, 1);
break;
}
}
for (var i = 0; i < netDebuggers.length; ++i)
{
if (netDebuggers[i] == debuggr)
{
netDebuggers.splice(i, 1);
break;
}
}
for (var i = 0; i < scriptListeners.length; ++i)
{
if (scriptListeners[i] == debuggr)
{
scriptListeners.splice(i, 1);
break;
}
}
if (debuggers.length == 0)
this.disableDebugger();
return debuggers.length;
},
lockDebugger: function()
{
if (this.locked)
return;
this.locked = true;
dispatch(debuggers, "onLock", [true]);
},
unlockDebugger: function()
{
if (!this.locked)
return;
this.locked = false;
dispatch(debuggers, "onLock", [false]);
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
forceGarbageCollection: function()
{
jsd.GC(); // Force the engine to perform garbage collection.
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
enterNestedEventLoop: function(callback)
{
dispatch(netDebuggers, "suspendActivity");
fbs.nestedEventLoopDepth = jsd.enterNestedEventLoop({
onNest: function()
{
dispatch(netDebuggers, "resumeActivity");
callback.onNest();
}
});
dispatch(netDebuggers, "resumeActivity");
return fbs.nestedEventLoopDepth;
},
exitNestedEventLoop: function()
{
dispatch(netDebuggers, "suspendActivity");
try
{
return jsd.exitNestedEventLoop();
}
catch (exc)
{
}
},
halt: function(debuggr)
{
haltDebugger = debuggr;
},
step: function(mode, startFrame, stayOnDebuggr)
{
stepMode = mode;
stepFrame = startFrame;
stepFrameCount = countFrames(startFrame);
stepFrameLineId = stepFrameCount + startFrame.script.fileName + startFrame.line;
stepStayOnDebuggr = stayOnDebuggr;
},
suspend: function(stayOnDebuggr, context)
{
stepMode = STEP_SUSPEND;
stepFrameLineId = null;
stepStayOnDebuggr = stayOnDebuggr;
dispatch(debuggers, "onBreakingNext", [stayOnDebuggr, context]);
this.hookInterrupts();
},
runUntil: function(sourceFile, lineNo, startFrame, debuggr)
{
runningUntil = this.addBreakpoint(BP_UNTIL, sourceFile, lineNo, null, debuggr);
stepFrameCount = countFrames(startFrame);
stepFrameLineId = stepFrameCount + startFrame.script.fileName + startFrame.line;
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
setBreakpoint: function(sourceFile, lineNo, props, debuggr)
{
var bp = this.addBreakpoint(BP_NORMAL, sourceFile, lineNo, props, debuggr);
if (bp)
{
dispatch(debuggers, "onToggleBreakpoint", [sourceFile.href, lineNo, true, bp]);
return true;
}
return false;
},
clearBreakpoint: function(url, lineNo)
{
var bp = this.removeBreakpoint(BP_NORMAL, url, lineNo);
if (bp)
dispatch(debuggers, "onToggleBreakpoint", [url, lineNo, false, bp]);
else
{
}
},
enableBreakpoint: function(url, lineNo)
{
var bp = this.findBreakpoint(url, lineNo);
if (bp && bp.type & BP_NORMAL)
{
bp.disabled &= ~BP_NORMAL;
dispatch(debuggers, "onToggleBreakpoint", [url, lineNo, true, bp]);
--disabledCount;
}
else {
}
},
disableBreakpoint: function(url, lineNo)
{
var bp = this.findBreakpoint(url, lineNo);
if (bp && bp.type & BP_NORMAL)
{
bp.disabled |= BP_NORMAL;
++disabledCount;
dispatch(debuggers, "onToggleBreakpoint", [url, lineNo, true, bp]);
}
else
{
}
},
isBreakpointDisabled: function(url, lineNo)
{
var bp = this.findBreakpoint(url, lineNo);
if (bp && bp.type & BP_NORMAL)
return bp.disabled & BP_NORMAL;
else
return false;
},
setBreakpointCondition: function(sourceFile, lineNo, condition, debuggr)
{
var bp = this.findBreakpoint(sourceFile.href, lineNo);
if (!bp)
{
bp = this.addBreakpoint(BP_NORMAL, sourceFile, lineNo, null, debuggr);
}
if (!bp)
return;
if (bp.hitCount <= 0 )
{
if (bp.condition && !condition)
{
--conditionCount;
}
else if (condition && !bp.condition)
{
++conditionCount;
}
}
bp.condition = condition;
dispatch(debuggers, "onToggleBreakpoint", [sourceFile.href, lineNo, true, bp]);
},
getBreakpointCondition: function(url, lineNo)
{
var bp = this.findBreakpoint(url, lineNo);
return bp ? bp.condition : "";
},
clearAllBreakpoints: function(sourceFiles)
{
for (var i = 0; i < sourceFiles.length; ++i)
{
var url = sourceFiles[i].href;
if (!url)
continue;
var urlBreakpoints = breakpoints[url];
if (!urlBreakpoints)
continue;
var removals = urlBreakpoints.length;
for (var j = 0; j < removals; ++j)
{
var bp = urlBreakpoints[0]; // this one will be spliced out each time
this.clearBreakpoint(url, bp.lineNo);
}
}
},
enumerateBreakpoints: function(url, cb) // url is sourceFile.href, not jsd script.fileName
{
if (url)
{
var urlBreakpoints = breakpoints[url];
if (urlBreakpoints)
{
for (var i = 0; i < urlBreakpoints.length; ++i)
{
var bp = urlBreakpoints[i];
if (bp.type & BP_NORMAL)
{
if (bp.scriptsWithBreakpoint && bp.scriptsWithBreakpoint.length > 0)
{
for (var j = 0; j < bp.scriptsWithBreakpoint.length; j++)
{
var rc = cb.call(url, bp.lineNo, bp, bp.scriptsWithBreakpoint[j]);
if (rc)
return [bp];
}
} else {
var rc = cb.call(url, bp.lineNo, bp);
if (rc)
return [bp];
}
}
}
}
}
else
{
var bps = [];
for (var url in breakpoints)
bps.push(this.enumerateBreakpoints(url, cb));
return bps;
}
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// error breakpoints are a way of selecting breakpoint from the Console
//
setErrorBreakpoint: function(sourceFile, lineNo, debuggr)
{
var url = sourceFile.href;
var index = this.findErrorBreakpoint(url, lineNo);
if (index == -1)
{
this.setBreakpoint(sourceFile, lineNo, null, debuggr);
errorBreakpoints.push({href: url, lineNo: lineNo, type: BP_ERROR });
dispatch(debuggers, "onToggleErrorBreakpoint", [url, lineNo, true, debuggr]);
}
},
clearErrorBreakpoint: function(sourceFile, lineNo, debuggr)
{
var url = sourceFile.href;
var index = this.findErrorBreakpoint(url, lineNo);
if (index != -1)
{
this.clearBreakpoint(url, lineNo);
errorBreakpoints.splice(index, 1);
dispatch(debuggers, "onToggleErrorBreakpoint", [url, lineNo, false, debuggr]);
}
},
hasErrorBreakpoint: function(url, lineNo)
{
return this.findErrorBreakpoint(url, lineNo) != -1;
},
enumerateErrorBreakpoints: function(url, cb)
{
if (url)
{
for (var i = 0; i < errorBreakpoints.length; ++i)
{
var bp = errorBreakpoints[i];
if (bp.href == url)
cb.call(bp.href, bp.lineNo, bp);
}
}
else
{
for (var i = 0; i < errorBreakpoints.length; ++i)
{
var bp = errorBreakpoints[i];
cb.call(bp.href, bp.lineNo, bp);
}
}
},
findErrorBreakpoint: function(url, lineNo)
{
for (var i = 0; i < errorBreakpoints.length; ++i)
{
var bp = errorBreakpoints[i];
if (bp.lineNo == lineNo && bp.href == url)
return i;
}
return -1;
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
traceAll: function(urls, debuggr)
{
this.hookCalls(debuggr.onFunctionCall, false); // call on all passed urls
},
untraceAll: function(debuggr)
{
jsd.functionHook = null; // undo hookCalls()
},
traceCalls: function(sourceFile, lineNo, debuggr)
{
var bp = this.monitor(sourceFile, lineNo, debuggr); // set a breakpoint on the starting point
bp.type |= BP_TRACE;
// when we hit the bp in onBreakPoint we being tracing.
},
untraceCalls: function(sourceFile, lineNo, debuggr)
{
var bp = lineNo != -1 ? this.findBreakpoint(url, lineNo) : null;
if (bp)
{
bp.type &= ~BP_TRACE;
this.unmonitor(sourceFile, lineNo);
}
},
monitor: function(sourceFile, lineNo, debuggr)
{
if (lineNo == -1)
return null;
var bp = this.addBreakpoint(BP_MONITOR, sourceFile, lineNo, null, debuggr);
if (bp)
{
++monitorCount;
dispatch(debuggers, "onToggleMonitor", [sourceFile.href, lineNo, true]);
}
return bp;
},
unmonitor: function(sourceFile, lineNo)
{
if (lineNo != -1 && this.removeBreakpoint(BP_MONITOR, sourceFile.href, lineNo))
{
--monitorCount;
dispatch(debuggers, "onToggleMonitor", [sourceFile.href, lineNo, false]);
}
},
isMonitored: function(url, lineNo)
{
var bp = lineNo != -1 ? this.findBreakpoint(url, lineNo) : null;
return bp && bp.type & BP_MONITOR;
},
enumerateMonitors: function(url, cb)
{
if (url)
{
var urlBreakpoints = breakpoints[url];
if (urlBreakpoints)
{
for (var i = 0; i < urlBreakpoints.length; ++i)
{
var bp = urlBreakpoints[i];
if (bp.type & BP_MONITOR)
cb.call(url, bp.lineNo, bp);
}
}
}
else
{
for (var url in breakpoints)
this.enumerateBreakpoints(url, cb);
}
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
enumerateScripts: function(length)
{
var scripts = [];
jsd.enumerateScripts( {
enumerateScript: function(script) {
var fileName = script.fileName;
if ( !isFilteredURL(fileName) ) {
scripts.push(script);
}
}
});
length.value = scripts.length;
return scripts;
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
startProfiling: function()
{
if (!this.profiling)
{
this.profiling = true;
profileStart = new Date();
jsd.flags |= COLLECT_PROFILE_DATA;
}
++profileCount;
},
stopProfiling: function()
{
if (--profileCount == 0)
{
jsd.flags &= ~COLLECT_PROFILE_DATA;
var t = profileStart.getTime();
this.profiling = false;
profileStart = null;
return new Date().getTime() - t;
}
else
return -1;
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
enableDebugger: function()
{
if (waitingForTimer)
{
timer.cancel();
waitingForTimer = false;
}
if (enabledDebugger)
return;
enabledDebugger = true;
this.obeyPrefs();
if (!jsd)
{
jsd = DebuggerService.getService(jsdIDebuggerService);
}
if (!jsd.isOn)
{
jsd.on(); // this should be the only call to jsd.on().
jsd.flags |= DISABLE_OBJECT_TRACE;
if (jsd.pauseDepth && FBTrace.DBG_FBS_ERRORS)
FBTrace.sysout("fbs.enableDebugger found non-zero jsd.pauseDepth !! "+jsd.pauseDepth)
}
if (!this.filterChrome)
this.createChromeBlockingFilters();
var active = fbs.isJSDActive();
dispatch(clients, "onJSDActivate", [active, "fbs enableDebugger"]);
this.hookScripts();
},
obeyPrefs: function()
{
this.showStackTrace = prefs.getBoolPref("extensions.firebug.service.showStackTrace");
this.breakOnErrors = prefs.getBoolPref("extensions.firebug.service.breakOnErrors");
this.trackThrowCatch = prefs.getBoolPref("extensions.firebug.service.trackThrowCatch");
this.scriptsFilter = prefs.getCharPref("extensions.firebug.service.scriptsFilter");
this.filterSystemURLs = prefs.getBoolPref("extensions.firebug.service.filterSystemURLs"); // may not be exposed to users
FirebugPrefsObserver.syncFilter();
try {
// CREATION and BP generate a huge trace
}
catch (exc)
{
FBTrace.sysout("firebug-service: constructor getBoolPrefs FAILED with exception=",exc);
}
},
disableDebugger: function()
{
if (!enabledDebugger)
return;
if (!timer) // then we probably shutdown
return;
enabledDebugger = false;
if (jsd.isOn)
{
jsd.pause();
fbs.unhookScripts();
while (jsd.pauseDepth > 0) // unwind completely
jsd.unPause();
fbs.pauseDepth = 0;
jsd.off();
}
var active = fbs.isJSDActive();
dispatch(clients, "onJSDDeactivate", [active, "fbs disableDebugger"]);
fbs.onXScriptCreatedByTag = {}; // clear any uncleared top level scripts
},
pause: function() // must support multiple calls
{
if (!enabledDebugger)
return "not enabled";
var rejection = [];
dispatch(clients, "onPauseJSDRequested", [rejection]);
if (rejection.length == 0) // then everyone wants to pause
{
if (fbs.pauseDepth == 0) // don't pause if we are paused.
{
fbs.pauseDepth++;
jsd.pause();
fbs.unhookScripts();
}
var active = fbs.isJSDActive();
dispatch(clients, "onJSDDeactivate", [active, "pause depth "+jsd.pauseDepth]);
}
else // we don't want to pause
{
while (fbs.pauseDepth > 0) // make sure we are not paused.
fbs.unPause();
fbs.pauseDepth = 0;
}
return fbs.pauseDepth;
},
unPause: function()
{
if (fbs.pauseDepth > 0)
{
fbs.pauseDepth--;
fbs.hookScripts();
var depth = jsd.unPause();
var active = fbs.isJSDActive();
dispatch(clients, "onJSDActivate", [active, "unpause depth"+jsd.pauseDepth]);
}
else // we were not paused.
{
}
return fbs.pauseDepth;
},
isJSDActive: function()
{
return (jsd && jsd.isOn && (jsd.pauseDepth == 0) );
},
broadcast: function(message, args) // re-transmit the message (string) with args [objs] to XUL windows.
{
dispatch(clients, message, args);
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
normalizeURL: function(url)
{
// For some reason, JSD reports file URLs like "file:/" instead of "file:///", so they
// don't match up with the URLs we get back from the DOM
return url ? url.replace(/file:\/([^/])/, "file:///$1") : "";
},
denormalizeURL: function(url)
{
// This should not be called.
return url ? url.replace(/file:\/\/\//, "file:/") : "";
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// jsd Hooks
// When (debugger keyword and not halt)||(bp and BP_UNTIL) || (onBreakPoint && no conditions)
// || interuptHook. rv is ignored
onBreak: function(frame, type, rv)
{
try
{
// avoid step_out from web page to chrome
if (type==jsdIExecutionHook.TYPE_INTERRUPTED && stepStayOnDebuggr)
{
var debuggr = this.reFindDebugger(frame, stepStayOnDebuggr);
if (!debuggr)
{
// This frame is not for the debugger we want
if (stepMode == STEP_OVER || stepMode == STEP_OUT) // then we are in the debuggr we want and returned in to one we don't
{
this.stopStepping(); // run, you are free.
}
return RETURN_CONTINUE; // This means that we will continue to take interrupts until when?
}
else
{
if (stepMode == STEP_SUSPEND) // then we have interrupted the outerFunction
{
var scriptTag = frame.script.tag;
if (scriptTag in this.onXScriptCreatedByTag) // yes, we have to create the sourceFile
this.onBreakpoint(frame, type, rv); // TODO refactor so we don't get mixed up
}
}
}
else
{
var debuggr = this.findDebugger(frame);
}
if (debuggr)
return this.breakIntoDebugger(debuggr, frame, type);
}
catch(exc)
{
ERROR("onBreak failed: "+exc);
}
return RETURN_CONTINUE;
},
// When engine encounters debugger keyword (only)
onDebugger: function(frame, type, rv)
{
try {
if (haltDebugger)
{
var debuggr = haltDebugger;
haltDebugger = null;
return debuggr.onHalt(frame);
}
else
return this.onBreak(frame, type, rv);
}
catch(exc)
{
ERROR("onDebugger failed: "+exc);
return RETURN_CONTINUE;
}
},
// when the onError handler returns false
onDebug: function(frame, type, rv)
{
if ( isFilteredURL(frame.script.fileName) )
return RETURN_CONTINUE;
try
{
var debuggr = (reportNextError || breakOnNextError) ? this.findDebugger(frame) : null;
if (reportNextError)
{
reportNextError = false;
if (debuggr)
{
var hookReturn = debuggr.onError(frame, errorInfo);
if (hookReturn >=0)
return hookReturn;
else if (hookReturn==-1)
breakOnNextError = true;
if (breakOnNextError)
debuggr = this.reFindDebugger(frame, debuggr);
}
}
if (breakOnNextError)
{
breakOnNextError = false;
if (debuggr)
return this.breakIntoDebugger(debuggr, frame, type);
}
} catch (exc) {
ERROR("onDebug failed: "+exc);
}
return RETURN_CONTINUE;
},
onBreakpoint: function(frame, type, val)
{
var scriptTag = frame.script.tag;
if (scriptTag in this.onXScriptCreatedByTag)
{
var onXScriptCreated = this.onXScriptCreatedByTag[scriptTag];
delete this.onXScriptCreatedByTag[scriptTag];
frame.script.clearBreakpoint(0);
try {
var sourceFile = onXScriptCreated(frame, type, val);
} catch (e) {
FBTrace.sysout("onBreakpoint called onXScriptCreated and it didn't end well:",e);
}
if (!sourceFile || !sourceFile.breakOnZero || sourceFile.breakOnZero != scriptTag)
return RETURN_CONTINUE;
else // sourceFile.breakOnZero matches the script we have halted.
{
}
}
var bp = this.findBreakpointByScript(frame.script, frame.pc);
if (bp)
{
// See issue 1179, should not break if we resumed from a single step and have not advanced.
if (disabledCount || monitorCount || conditionCount || runningUntil)
{
if (bp.type & BP_MONITOR && !(bp.disabled & BP_MONITOR))
{
if (bp.type & BP_TRACE && !(bp.disabled & BP_TRACE) )
this.hookCalls(bp.debugger.onFunctionCall, true);
else
bp.debugger.onMonitorScript(frame);
}
if (bp.type & BP_UNTIL)
{
this.stopStepping();
if (bp.debugger)
return this.breakIntoDebugger(bp.debugger, frame, type);
}
else if (!(bp.type & BP_NORMAL) || bp.disabled & BP_NORMAL)
{
return RETURN_CONTINUE;
}
else if (bp.type & BP_NORMAL)
{
var passed = testBreakpoint(frame, bp);
if (!passed)
return RETURN_CONTINUE;
}
// type was normal, but passed test
}
else // not special, just break for sure
return this.breakIntoDebugger(bp.debugger, frame, type);
}
else
{
}
if (runningUntil)
return RETURN_CONTINUE;
else
return this.onBreak(frame, type, val);
},
onThrow: function(frame, type, rv)
{
if ( isFilteredURL(frame.script.fileName) )
return RETURN_CONTINUE_THROW;
if (rv && rv.value && rv.value.isValid)
{
var value = rv.value;
if (value.jsClassName == "Error" && reTooMuchRecursion.test(value.stringValue))
{
if (fbs._lastErrorCaller)
{
if (fbs._lastErrorCaller == frame.script.tag) // then are unwinding recursion
{
fbs._lastErrorCaller = frame.callingFrame ? frame.callingFrame.script.tag : null;
return RETURN_CONTINUE_THROW;
}
}
else
{
fbs._lastErrorCaller = frame.callingFrame.script.tag;
frame = fbs.discardRecursionFrames(frame);
// go on to process the throw.
}
}
else
{
delete fbs._lastErrorCaller; // throw is not recursion
}
}
else
{
delete fbs._lastErrorCaller; // throw is not recursion either
}
// Remember the error where the last exception is thrown - this will
// be used later when the console service reports the error, since
// it doesn't currently report the window where the error occurred
this._lastErrorWindow = getFrameGlobal(frame);
if (this.showStackTrace) // store these in case the throw is not caught
{
var debuggr = this.findDebugger(frame); // sets debuggr.breakContext
if (debuggr)
{
fbs._lastErrorScript = frame.script;
fbs._lastErrorLine = frame.line;
fbs._lastErrorDebuggr = debuggr;
fbs._lastErrorContext = debuggr.breakContext; // XXXjjb this is bad API
}
else
delete fbs._lastErrorDebuggr;
}
if (fbs.trackThrowCatch)
{
var debuggr = this.findDebugger(frame);
if (debuggr)
return debuggr.onThrow(frame, rv);
}
return RETURN_CONTINUE_THROW;
},
onError: function(message, fileName, lineNo, pos, flags, errnum, exc)
{
errorInfo = { errorMessage: message, sourceName: fileName, lineNumber: lineNo,
message: message, fileName: fileName, lineNo: lineNo,
columnNumber: pos, flags: flags, category: "js", errnum: errnum, exc: exc };
if (message=="out of memory") // bail
{
return true;
}
if (this.showStackTrace)
{
reportNextError = true;
return false; // Drop into onDebug, sometimes only
}
else
{
return !this.needToBreakForError(fileName, lineNo);
}
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
onEventScriptCreated: function(frame, type, val, noNestTest)
{
if (fbs.showEvents)
{
try
{
if (!noNestTest)
{
// In onScriptCreated we saw a script with baseLineNumber = 1. We marked it as event and nested.
// Now we know its event, not nested.
if (fbs.nestedScriptStack.length > 0)
{
fbs.nestedScriptStack.removeElementAt(0);
}
else
{
}
}
var debuggr = fbs.findDebugger(frame); // sets debuggr.breakContext
if (debuggr)
{
var sourceFile = debuggr.onEventScriptCreated(frame, frame.script, fbs.nestedScriptStack.enumerate());
fbs.resetBreakpoints(sourceFile);
}
else
{
}
} catch(exc) {
}
}
fbs.clearNestedScripts();
return sourceFile;
},
onEvalScriptCreated: function(frame, type, val)
{
if (fbs.showEvals)
{
try
{
if (!frame.callingFrame)
{
return fbs.onEventScriptCreated(frame, type, val, true);
}
// In onScriptCreated we found a no-name script, set a bp in PC=0, and a flag.
// onBreakpoint saw the flag, cleared the flag, and sent us here.
// Start by undoing our damage
var outerScript = frame.script;
var debuggr = fbs.findDebugger(frame); // sets debuggr.breakContext
if (debuggr)
{
var sourceFile = debuggr.onEvalScriptCreated(frame, outerScript, fbs.nestedScriptStack.enumerate());
fbs.resetBreakpoints(sourceFile);
}
else
{
}
}
catch (exc)
{
ERROR("onEvalScriptCreated failed: "+exc);
}
}
fbs.clearNestedScripts();
return sourceFile;
},
onTopLevelScriptCreated: function(frame, type, val)
{
try
{
// In onScriptCreated we may have found a script at baseLineNumber=1
// Now we know its not an event
if (fbs.nestedScriptStack.length > 0)
{
var firstScript = fbs.nestedScriptStack.queryElementAt(0, jsdIScript);
if (firstScript.tag in fbs.onXScriptCreatedByTag)
{
delete fbs.onXScriptCreatedByTag[firstScript.tag];
if (firstScript.isValid)
firstScript.clearBreakpoint(0);
}
}
// On compilation of a top-level (global-appending) function.
// After this top-level script executes we lose the jsdIScript so we can't build its line table.
// Therefore we need to build it here.
var debuggr = fbs.findDebugger(frame); // sets debuggr.breakContext
if (debuggr)
{
var sourceFile = debuggr.onTopLevelScriptCreated(frame, frame.script, fbs.nestedScriptStack.enumerate());
fbs.resetBreakpoints(sourceFile, frame.script.baseLineNumber+frame.script.lineExtent);
}
else
{
// modules end up here?
}
}
catch (exc)
{
FBTrace.sysout("onTopLevelScriptCreated FAILED: ", exc);
ERROR("onTopLevelScriptCreated Fails: "+exc);
}
fbs.clearNestedScripts();
return sourceFile;
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
clearNestedScripts: function()
{
var innerScripts = fbs.nestedScriptStack.enumerate();
while (innerScripts.hasMoreElements())
{
var script = innerScripts.getNext();
if (script.isValid && script.baseLineNumber == 1)
{
script.clearBreakpoint(0);
if (this.onXScriptCreatedByTag[script.tag])
delete this.onXScriptCreatedByTag[script.tag];
}
}
fbs.nestedScriptStack.clear();
},
onScriptCreated: function(script)
{
if (!fbs)
{
return;
}
try
{
var fileName = script.fileName;
if (isFilteredURL(fileName))
{
try {
} catch (exc) { /*Bug 426692 */ }
return;
}
// reset tracing flags on first unfiltered filename
if (!FBTrace.DBG_FF_START && !fbs.firstUnfilteredFilename)
{
fbs.firstUnfilteredFilename = true;
FBTrace.DBG_FBS_BP = fbs.resetBP ? true : false;
FBTrace.DBG_FBS_CREATION = fbs.resetCreation ? true : false;
}
if (fbs.pendingXULFileName && fbs.pendingXULFileName != script.fileName)
fbs.flushXUL();
if (!script.functionName) // top or eval-level
{
// We need to detect eval() and grab its source.
var hasCaller = fbs.createdScriptHasCaller();
if (hasCaller)
{
// components end up here
fbs.onXScriptCreatedByTag[script.tag] = this.onEvalScriptCreated;
}
else
fbs.onXScriptCreatedByTag[script.tag] = this.onTopLevelScriptCreated;
script.setBreakpoint(0);
}
else if (script.baseLineNumber == 1)
{
// could be a 1) Browser-generated event handler or 2) a nested script at the top of a file
// One way to tell is assume both then wait to see which we hit first:
// 1) bp at pc=0 for this script or 2) for a top-level on at the same filename
fbs.onXScriptCreatedByTag[script.tag] = this.onEventScriptCreated; // for case 1
script.setBreakpoint(0);
fbs.nestedScriptStack.appendElement(script, false); // for case 2
}
else if( reXUL.test(script.fileName) )
{
fbs.pendingXULFileName = script.fileName; // if these were different, we would already have called flushXUL()
fbs.nestedScriptStack.appendElement(script, false);
}
else
{
fbs.nestedScriptStack.appendElement(script, false);
dispatch(scriptListeners,"onScriptCreated",[script, fileName, script.baseLineNumber]);
}
}
catch(exc)
{
ERROR("onScriptCreated failed: "+exc);
FBTrace.sysout("onScriptCreated failed: ", exc);
}
},
flushXUL: function()
{
for ( var i = debuggers.length - 1; i >= 0; i--)
{
try
{
var debuggr = debuggers[i];
if (debuggr.onXULScriptCreated)
debuggr.onXULScriptCreated(fbs.pendingXULFileName, fbs.nestedScriptStack.enumerate());
}
catch (exc)
{
FBTrace.sysout("firebug-service flushXUL FAILS: ",exc);
}
}
delete fbs.pendingXULFileName;
fbs.clearNestedScripts();
},
createdScriptHasCaller: function()
{
var frame = Components.stack; // createdScriptHasCaller
frame = frame.caller; // onScriptCreated
if (!frame) return frame;
frame = frame.caller; // hook apply
if (!frame) return frame;
frame = frame.caller; // native interpret?
if (!frame) return frame;
frame = frame.caller; // our creator ... or null if we are top level
return frame;
},
onScriptDestroyed: function(script)
{
if (!fbs)
return;
if (script.tag in fbs.onXScriptCreatedByTag)
delete fbs.onXScriptCreatedByTag[script.tag];
try
{
var fileName = script.fileName;
if (isFilteredURL(fileName))
return;
dispatch(scriptListeners,"onScriptDestroyed",[script]);
}
catch(exc)
{
ERROR("onScriptDestroyed failed: "+exc);
FBTrace.sysout("onScriptDestroyed failed: ", exc);
}
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
createFilter: function(pattern, pass)
{
var filter = {
globalObject: null,
flags: pass ? (jsdIFilter.FLAG_ENABLED | jsdIFilter.FLAG_PASS) : jsdIFilter.FLAG_ENABLED,
urlPattern: pattern,
startLine: 0,
endLine: 0
};
return filter;
},
setChromeBlockingFilters: function()
{
if (!fbs.isChromeBlocked)
{
jsd.appendFilter(this.noFilterHalter); // must be first
jsd.appendFilter(this.filterChrome);
jsd.appendFilter(this.filterComponents);
jsd.appendFilter(this.filterFirebugComponents);
jsd.appendFilter(this.filterModules);
jsd.appendFilter(this.filterStringBundle);
jsd.appendFilter(this.filterPrettyPrint);
jsd.appendFilter(this.filterWrapper);
for (var i = 0; i < this.componentFilters.length; i++)
jsd.appendFilter(this.componentFilters[i]);
fbs.isChromeBlocked = true;
}
},
removeChromeBlockingFilters: function()
{
if (fbs.isChromeBlocked)
{
jsd.removeFilter(this.filterChrome);
jsd.removeFilter(this.filterComponents);
jsd.removeFilter(this.filterFirebugComponents);
jsd.removeFilter(this.filterModules);
jsd.removeFilter(this.filterStringBundle);
jsd.removeFilter(this.filterPrettyPrint);
jsd.removeFilter(this.filterWrapper);
jsd.removeFilter(this.noFilterHalter);
for (var i = 0; i < this.componentFilters.length; i++)
jsd.removeFilter(this.componentFilters[i]);
fbs.isChromeBlocked = false;
}
},
createChromeBlockingFilters: function() // call after components are loaded.
{
try
{
this.filterModules = this.createFilter("*/firefox/modules/*");
this.filterComponents = this.createFilter("*/firefox/components/*");
this.filterFirebugComponents = this.createFilter("*/components/firebug-*");
this.filterStringBundle = this.createFilter("XStringBundle");
this.filterChrome = this.createFilter("chrome://*");
this.filterPrettyPrint = this.createFilter("x-jsd:ppbuffer*");
this.filterWrapper = this.createFilter("XPCSafeJSObjectWrapper.cpp");
this.noFilterHalter = this.createFilter("chrome://firebug/content/debuggerHalter.js", true);
// jsdIFilter does not allow full regexp matching.
// So to filter components, we filter their directory names, which we obtain by looking for
// scripts that match regexps
var componentsUnfound = [];
for( var i = 0; i < COMPONENTS_FILTERS.length; ++i )
{
componentsUnfound.push(COMPONENTS_FILTERS[i]);
}
this.componentFilters = [];
jsd.enumerateScripts( {
enumerateScript: function(script) {
var fileName = script.fileName;
for( var i = 0; i < componentsUnfound.length; ++i )
{
if ( componentsUnfound[i].test(fileName) )
{
var match = componentsUnfound[i].exec(fileName);
fbs.componentFilters.push(fbs.createFilter(match[1]));
componentsUnfound.splice(i, 1);
return;
}
}
}
});
} catch (exc) {
FBTrace.sysout("createChromeblockingFilters fails >>>>>>>>>>>>>>>>> "+exc, exc);
}
},
traceFilters: function(from)
{
FBTrace.sysout("fbs.traceFilters from "+from);
jsd.enumerateFilters({ enumerateFilter: function(filter)
{
FBTrace.sysout("jsdIFilter "+filter.urlPattern, filter);
}});
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
getJSContexts: function()
{
var enumeratedContexts = [];
jsd.enumerateContexts( {enumerateContext: function(jscontext)
{
try
{
if (!jscontext.isValid)
return;
var wrappedGlobal = jscontext.globalObject;
if (!wrappedGlobal)
return;
var unwrappedGlobal = wrappedGlobal.getWrappedValue();
if (!unwrappedGlobal)
return;
var global = new XPCNativeWrapper(unwrappedGlobal);
if (global)
{
var document = global.document;
if (document)
{
}
else
{
return; // skip these
}
}
else
{
return; // skip this
}
enumeratedContexts.push(jscontext);
}
catch(e)
{
FBTrace.sysout("jscontext dump FAILED "+e, e);
}
}});
return enumeratedContexts;
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
findDebugger: function(frame)
{
if (debuggers.length < 1)
return;
var checkFrame = frame;
while (checkFrame) // We may stop in a component, but want the callers Window
{
var frameScopeRoot = getFrameScopeRoot(checkFrame);
if (frameScopeRoot)
break;
checkFrame = checkFrame.callingFrame;
}
if (!checkFrame && FBTrace.DBG_FBS_FINDDEBUGGER)
FBTrace.sysout("fbs.findDebugger fell thru bottom of stack", frame);
// frameScopeRoot should be the top window for the scope of the frame function
// or null
fbs.last_debuggr = fbs.askDebuggersForSupport(frameScopeRoot, frame);
if (fbs.last_debuggr)
return fbs.last_debuggr;
else
return null;
},
isChromebug: function(global)
{
// TODO this is a kludge: isFilteredURL stops users from seeing firebug but chromebug has to disable the filter
var location = fbs.getLocationSafe(global);
if (location)
{
if (location.indexOf("chrome://chromebug/") >= 0 || location.indexOf("chrome://fb4cb/") >= 0)
return true;
}
return false;
},
getLocationSafe: function(global)
{
try
{
if (global && global.location) // then we have a window, it will be an nsIDOMWindow, right?
return global.location.toString();
else if (global && global.tag)
return "global_tag_"+global.tag;
}
catch (exc)
{
// FF3 gives (NS_ERROR_INVALID_POINTER) [nsIDOMLocation.toString]
}
return null;
},
askDebuggersForSupport: function(global, frame)
{
if (global && fbs.isChromebug(global))
return false;
for ( var i = debuggers.length - 1; i >= 0; i--)
{
try
{
var debuggr = debuggers[i];
if (debuggr.supportsGlobal(global, frame))
{
if (!debuggr.breakContext)
FBTrace.sysout("Debugger with no breakContext:",debuggr.supportsGlobal);
return debuggr;
}
}
catch (exc)
{
FBTrace.sysout("firebug-service askDebuggersForSupport FAILS: "+exc,exc);
}
}
return null;
},
dumpIValue: function(value)
{
var listValue = {value: null}, lengthValue = {value: 0};
value.getProperties(listValue, lengthValue);
for (var i = 0; i < lengthValue.value; ++i)
{
var prop = listValue.value[i];
try {
var name = unwrapIValue(prop.name);
FBTrace.sysout(i+"]"+name+"="+unwrapIValue(prop.value));
}
catch (e)
{
FBTrace.sysout(i+"]"+e);
}
}
},
reFindDebugger: function(frame, debuggr)
{
var frameScopeRoot = getFrameScopeRoot(frame);
if (frameScopeRoot && debuggr.supportsGlobal(frameScopeRoot, frame)) return debuggr;
return null;
},
discardRecursionFrames: function(frame)
{
var i = 0;
var rest = 0;
var mark = frame; // a in abcabcabcdef
var point = frame;
while (point = point.callingFrame)
{
i++;
if (point.script.tag == mark.script.tag) // then we found a repeating caller abcabcdef
{
mark = point;
rest = i;
}
}
// here point is null and mark is the last repeater, abcdef
return mark;
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// jsd breakpoints are on a PC in a jsdIScript
// Users breakpoint on a line of source
// Because test.js can be included multiple times, the URL+line number from the UI is not unique.
// sourcefile.href != script.fileName, generally script.fileName cannot be used.
// If the source is compiled, then we have zero, one, or more jsdIScripts on a line.
// If zero, cannot break at that line
// If one, set a jsd breakpoint
// If more than one, set jsd breakpoint on each script
// Else we know that the source will be compiled before it is run.
// Save the sourceFile.href+line and set the jsd breakpoint when we compile
// Venkman called these "future" breakpoints
// We cannot prevent future breakpoints on lines that have no script. Break onCreate with error?
addBreakpoint: function(type, sourceFile, lineNo, props, debuggr)
{
var url = sourceFile.href;
var bp = this.findBreakpoint(url, lineNo);
if (bp && bp.type & type)
return null;
if (bp)
{
bp.type |= type;
if (debuggr)
bp.debugger = debuggr;
else
{
}
}
else
{
bp = this.recordBreakpoint(type, url, lineNo, debuggr, props);
fbs.setJSDBreakpoint(sourceFile, bp);
}
return bp;
},
recordBreakpoint: function(type, url, lineNo, debuggr, props)
{
var urlBreakpoints = breakpoints[url];
if (!urlBreakpoints)
breakpoints[url] = urlBreakpoints = [];
var bp = {type: type, href: url, lineNo: lineNo, disabled: 0,
debugger: debuggr,
condition: "", onTrue: true, hitCount: -1, hit: 0};
if (props)
{
bp.condition = props.condition;
bp.onTrue = props.onTrue;
bp.hitCount = props.hitCount;
if (bp.condition || bp.hitCount > 0)
++conditionCount;
if(props.disabled)
{
bp.disabled |= BP_NORMAL;
++disabledCount;
}
}
urlBreakpoints.push(bp);
++breakpointCount;
return bp;
},
removeBreakpoint: function(type, url, lineNo)
{
var urlBreakpoints = breakpoints[url];
if (!urlBreakpoints)
return false;
for (var i = 0; i < urlBreakpoints.length; ++i)
{
var bp = urlBreakpoints[i];
if (bp.lineNo == lineNo)
{
bp.type &= ~type;
if (!bp.type)
{
if (bp.scriptsWithBreakpoint)
{
for (var j = 0; j < bp.scriptsWithBreakpoint.length; j++)
{
var script = bp.scriptsWithBreakpoint[j];
if (script && script.isValid)
{
try
{
script.clearBreakpoint(bp.pc[j]);
}
catch (exc)
{
FBTrace.sysout("Firebug service failed to remove breakpoint in "+script.tag+" at lineNo="+lineNo+" pcmap:"+bp.pcmap);
}
}
}
}
// else this was a future breakpoint that never hit or a script that was GCed
urlBreakpoints.splice(i, 1);
--breakpointCount;
if (bp.disabled)
--disabledCount;
if (bp.condition || bp.hitCount > 0)
{
--conditionCount;
}
if (!urlBreakpoints.length)
delete breakpoints[url];
}
return bp;
}
}
return false;
},
findBreakpoint: function(url, lineNo)
{
var urlBreakpoints = breakpoints[url];
if (urlBreakpoints)
{
for (var i = 0; i < urlBreakpoints.length; ++i)
{
var bp = urlBreakpoints[i];
if (bp.lineNo == lineNo)
return bp;
}
}
return null;
},
// When we are called, scripts have been compiled so all relevant breakpoints are not "future"
findBreakpointByScript: function(script, pc)
{
for (var url in breakpoints)
{
var urlBreakpoints = breakpoints[url];
if (urlBreakpoints)
{
for (var i = 0; i < urlBreakpoints.length; ++i)
{
var bp = urlBreakpoints[i];
if (bp.scriptsWithBreakpoint)
{
for (var j = 0; j < bp.scriptsWithBreakpoint.length; j++)
{
if ( bp.scriptsWithBreakpoint[j] && (bp.scriptsWithBreakpoint[j].tag == script.tag) && (bp.pc[j] == pc) )
return bp;
}
}
}
}
}
return null;
},
resetBreakpoints: function(sourceFile, lastLineNumber) // the sourcefile has just been created after compile
{
// If the new script is replacing an old script with a breakpoint still
var url = sourceFile.href;
var urlBreakpoints = breakpoints[url];
if (urlBreakpoints)
{
for (var i = 0; i < urlBreakpoints.length; ++i)
{
var bp = urlBreakpoints[i];
fbs.setJSDBreakpoint(sourceFile, bp);
if (lastLineNumber && !bp.jsdLine && !(bp.disabled & BP_NORMAL) && (bp.lineNo < lastLineNumber))
{
fbs.disableBreakpoint(url, bp.lineNo);
}
}
}
else
{
}
},
setJSDBreakpoint: function(sourceFile, bp)
{
var scripts = sourceFile.getScriptsAtLineNumber(bp.lineNo);
if (!scripts)
{
if (!sourceFile.outerScript || !sourceFile.outerScript.isValid)
{
return;
}
scripts = [sourceFile.outerScript];
}
bp.scriptsWithBreakpoint = [];
bp.pc = [];
for (var i = 0; i < scripts.length; i++)
{
var script = scripts[i];
if (!script.isValid)
{
continue;
}
var pcmap = sourceFile.pcmap_type;
if (!pcmap)
{
pcmap = PCMAP_SOURCETEXT;
}
// we subtraced this offset when we showed the user so lineNo is a user line number; now we need to talk
// to jsd its line world
var jsdLine = bp.lineNo + sourceFile.getBaseLineOffset();
// test script.isLineExecutable(jsdLineNo, pcmap) ??
var isExecutable = false;
try {
isExecutable = script.isLineExecutable(jsdLine, pcmap);
} catch(e) {
// guess not then...
}
if (isExecutable)
{
var pc = script.lineToPc(jsdLine, pcmap);
var pcToLine = script.pcToLine(pc, pcmap); // avoid calling this unless we have to...
if (pcToLine == jsdLine)
{
script.setBreakpoint(pc);
bp.scriptsWithBreakpoint.push(script);
bp.pc.push(pc);
bp.pcmap = pcmap;
bp.jsdLine = jsdLine;
if (pc == 0) // signal the breakpoint handler to break for user
sourceFile.breakOnZero = script.tag;
}
else
{
}
}
else
{
}
}
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
breakIntoDebugger: function(debuggr, frame, type)
{
// Before we break, clear information about previous stepping session
this.stopStepping();
// Break into the debugger - execution will stop here until the user resumes
var returned;
try
{
returned = debuggr.onBreak(frame, type);
}
catch (exc)
{
ERROR(exc);
returned = RETURN_CONTINUE;
}
// Execution resumes now. Check if the user requested stepping and if so
// install the necessary hooks
hookFrameCount = countFrames(frame);
this.startStepping();
return returned;
},
needToBreakForError: function(fileName, lineNo)
{
return breakOnNextError =
this.breakOnErrors || this.findErrorBreakpoint(this.normalizeURL(fileName), lineNo) != -1;
},
startStepping: function()
{
if (!stepMode && !runningUntil)
return;
this.hookFunctions();
if (stepMode == STEP_OVER || stepMode == STEP_INTO)
this.hookInterrupts();
},
stopStepping: function()
{
stepMode = 0;
stepFrame = null;
stepFrameCount = 0;
stepFrameLineId = null;
if (runningUntil)
{
this.removeBreakpoint(BP_UNTIL, runningUntil.href, runningUntil.lineNo);
runningUntil = null;
}
jsd.interruptHook = null;
jsd.functionHook = null;
},
/*
* Returns a string describing the step mode or null for not stepping.
*/
getStepMode: function()
{
return getStepName(stepMode);
},
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
hookFunctions: function()
{
function functionHook(frame, type)
{
switch (type)
{
case TYPE_TOPLEVEL_START: // fall through
case TYPE_FUNCTION_CALL:
{
++hookFrameCount;
if (stepMode == STEP_OVER)
jsd.interruptHook = null;
if (stepMode == STEP_INTO) // normally step into will break in the interrupt handler, but not in event handlers.
{
fbs.stopStepping();
stepMode = STEP_SUSPEND; // break on next
fbs.hookInterrupts();
}
break;
}
case TYPE_TOPLEVEL_END: // fall through
case TYPE_FUNCTION_RETURN:
{
--hookFrameCount;
if (hookFrameCount == 0) { // stack empty
if ( (stepMode == STEP_INTO) || (stepMode == STEP_OVER) ) {
fbs.stopStepping();
stepMode = STEP_SUSPEND; // break on next
fbs.hookInterrupts();
}
else
{
fbs.stopStepping();
}
}
else if (stepMode == STEP_OVER)
{
if (hookFrameCount <= stepFrameCount)
fbs.hookInterrupts();
}
else if (stepMode == STEP_OUT)
{
if (hookFrameCount < stepFrameCount)
fbs.hookInterrupts();
}
break;
}
}
}
jsd.functionHook = { onCall: functionHook };
},
hookInterrupts: function()
{
function interruptHook(frame, type, rv)
{
/*if ( isFilteredURL(frame.script.fileName) ) // it does not seem feasible to use jsdIFilter-ing TODO try again
{
return RETURN_CONTINUE;
}
*/
// Sometimes the same line will have multiple interrupts, so check
// a unique id for the line and don't break until it changes
var frameLineId = hookFrameCount + frame.script.fileName + frame.line;
if (frameLineId != stepFrameLineId)
return fbs.onBreak(frame, type, rv);
else
return RETURN_CONTINUE;
}
jsd.interruptHook = { onExecute: interruptHook };
},
hookScripts: function()
{
jsd.scriptHook = {
onScriptCreated: hook(this.onScriptCreated),
onScriptDestroyed: hook(this.onScriptDestroyed)
};
if (fbs.filterSystemURLs)
fbs.setChromeBlockingFilters();
jsd.debuggerHook = { onExecute: hook(this.onDebugger, RETURN_CONTINUE) };
jsd.debugHook = { onExecute: hook(this.onDebug, RETURN_CONTINUE) };
jsd.breakpointHook = { onExecute: hook(this.onBreakpoint, RETURN_CONTINUE) };
jsd.throwHook = { onExecute: hook(this.onThrow, RETURN_CONTINUE_THROW) };
jsd.errorHook = { onError: hook(this.onError, true) };
},
unhookScripts: function()
{
jsd.scriptHook = null;
fbs.removeChromeBlockingFilters();
},
hookCalls: function(callBack, unhookAtBottom)
{
var contextCached = null;
function callHook(frame, type)
{
switch (type)
{
case TYPE_FUNCTION_CALL:
{
++hookFrameCount;
contextCached = callBack(contextCached, frame, hookFrameCount, true);
break;
}
case TYPE_FUNCTION_RETURN:
{
if(hookFrameCount <= 0) // ignore returns until we have started back in
return;
--hookFrameCount;
if (unhookAtBottom && hookFrameCount == 0) { // stack empty
jsd.functionHook = null;
}
contextCached = callBack(contextCached, frame, hookFrameCount, false);
break;
}
}
}
if (jsd.functionHook)
{
return;
}
hookFrameCount = 0;
jsd.functionHook = { onCall: callHook };
},
getJSD: function()
{
return jsd; // for debugging fbs
},
dumpFileTrack: function(moreFiles)
{
if (moreFiles)
trackFiles.merge(moreFiles);
trackFiles.dump();
},
};
function getStepName(mode)
{
if (mode==STEP_OVER) return "STEP_OVER";
if (mode==STEP_INTO) return "STEP_INTO";
if (mode==STEP_OUT) return "STEP_OUT";
if (mode==STEP_SUSPEND) return "STEP_SUSPEND";
else return "(not a step mode)";
}
// ************************************************************************************************
var FirebugFactory =
{
createInstance: function (outer, iid)
{
if (outer != null)
throw NS_ERROR_NO_AGGREGATION;
FirebugFactory.initializeService();
return (new FirebugService()).QueryInterface(iid);
},
initializeService: function()
{
if (!prefs)
prefs = PrefService.getService(nsIPrefBranch2);
var filterSystemURLs = prefs.getBoolPref("extensions.firebug.service.filterSystemURLs");
if (filterSystemURLs) // do not turn jsd on unless we want to see chrome
return;
try
{
var jsd = DebuggerService.getService(jsdIDebuggerService);
jsd.initAtStartup = false;
}
catch (exc)
{
}
}
};
// ************************************************************************************************
var FirebugModule =
{
registerSelf: function (compMgr, fileSpec, location, type)
{
compMgr = compMgr.QueryInterface(nsIComponentRegistrar);
compMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, CONTRACT_ID, fileSpec, location, type);
},
unregisterSelf: function(compMgr, fileSpec, location)
{
compMgr = compMgr.QueryInterface(nsIComponentRegistrar);
compMgr.unregisterFactoryLocation(CLASS_ID, location);
},
getClassObject: function (compMgr, cid, iid)
{
if (!iid.equals(nsIFactory))
throw NS_ERROR_NOT_IMPLEMENTED;
if (cid.equals(CLASS_ID))
return FirebugFactory;
throw NS_ERROR_NO_INTERFACE;
},
canUnload: function(compMgr)
{
return true;
}
};
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
function NSGetModule(compMgr, fileSpec)
{
return FirebugModule;
}
// ************************************************************************************************
// Local Helpers
// called by enumerateScripts, onThrow, onDebug, onScriptCreated/Destroyed.
function isFilteredURL(rawJSD_script_filename)
{
if (!rawJSD_script_filename)
return true;
if (fbs.filterConsoleInjections)
return true;
if (rawJSD_script_filename[0] == 'h')
return false;
if (rawJSD_script_filename == "XPCSafeJSObjectWrapper.cpp")
return true;
if (fbs.filterSystemURLs)
return systemURLStem(rawJSD_script_filename);
for (var i = 0; i < fbs.alwayFilterURLsStarting.length; i++)
if (rawJSD_script_filename.indexOf(fbs.alwayFilterURLsStarting[i]) != -1)
return true;
return false;
}
function systemURLStem(rawJSD_script_filename)
{
if (this.url_class) // attempt to optimize stream of similar urls
{
if ( rawJSD_script_filename.substr(0,this.url_class.length) == this.url_class )
return this.url_class;
}
this.url_class = deepSystemURLStem(rawJSD_script_filename);
return this.url_class;
}
function deepSystemURLStem(rawJSD_script_filename)
{
for( var i = 0; i < urlFilters.length; ++i )
{
var filter = urlFilters[i];
if ( rawJSD_script_filename.substr(0,filter.length) == filter )
return filter;
}
for( var i = 0; i < COMPONENTS_FILTERS.length; ++i )
{
if ( COMPONENTS_FILTERS[i].test(rawJSD_script_filename) )
{
var match = COMPONENTS_FILTERS[i].exec(rawJSD_script_filename);
urlFilters.push(match[1]); // cache this for future calls
return match[1];
}
}
return false;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
function dispatch(listeners, name, args)
{
var totalListeners = listeners.length;
for (var i = 0; i < totalListeners; ++i)
{
var listener = listeners[i];
if ( listener.hasOwnProperty(name) )
listener[name].apply(listener, args);
}
//if (FBTrace.DBG_FBS_ERRORS)
// FBTrace.sysout("fbs.dispatch "+name+" to "+listeners.length+" listeners");
}
function hook(fn, rv)
{
return function()
{
try
{
return fn.apply(fbs, arguments);
}
catch (exc)
{
var msg = "Error in hook: "+ exc +" fn=\n"+fn+"\n stack=\n";
for (var frame = Components.stack; frame; frame = frame.caller)
msg += frame.filename + "@" + frame.line + ";\n";
ERROR(msg);
return rv;
}
}
}
var lastWindowScope = null;
function getFrameScopeRoot(frame) // walk script scope chain to bottom, convert to Window if possible
{
var scope = frame.scope;
if (scope)
{
while(scope.jsParent)
scope = scope.jsParent;
// These are just determined by trial and error.
if (scope.jsClassName == "Window" || scope.jsClassName == "ChromeWindow" || scope.jsClassName == "ModalContentWindow")
{
lastWindowScope = new XPCNativeWrapper(scope.getWrappedValue());
return lastWindowScope;
}
if (scope.jsClassName == "DedicatedWorkerGlobalScope")
{
//var workerScope = new XPCNativeWrapper(scope.getWrappedValue());
//if (FBTrace.DBG_FBS_FINDDEBUGGER)
// FBTrace.sysout("fbs.getFrameScopeRoot found WorkerGlobalScope: "+scope.jsClassName, workerScope);
// https://bugzilla.mozilla.org/show_bug.cgi?id=507930 if (FBTrace.DBG_FBS_FINDDEBUGGER)
// FBTrace.sysout("fbs.getFrameScopeRoot found WorkerGlobalScope.location: "+workerScope.location, workerScope.location);
return null; // https://bugzilla.mozilla.org/show_bug.cgi?id=507783
}
if (scope.jsClassName == "Sandbox")
{
var proto = scope.jsPrototype;
if (proto.jsClassName == "XPCNativeWrapper")
proto = proto.jsParent;
if (proto.jsClassName == "Window")
return new XPCNativeWrapper(proto.getWrappedValue());
}
try
{
return new XPCNativeWrapper(scope.getWrappedValue());
}
catch(exc)
{
}
}
else
return null;
}
function getFrameGlobal(frame)
{
var jscontext = frame.executionContext;
if (!jscontext)
{
return getFrameWindow(frame);
}
try
{
var frameGlobal = new XPCNativeWrapper(jscontext.globalObject.getWrappedValue());
}
catch(exc)
{
}
if (frameGlobal)
return frameGlobal;
else
{
return getFrameWindow(frame);
}
}
function getFrameWindow(frame)
{
if (debuggers.length < 1) // too early, frame.eval will crash FF2
return;
try
{
var result = {};
frame.eval("window", "", 1, result);
var win = unwrapIValue(result.value);
if (win instanceof Ci.nsIDOMWindow)
return getRootWindow(win);
else
return getFrameScopeRoot(frame);
}
catch (exc)
{
return null;
}
}
function getRootWindow(win)
{
for (; win; win = win.parent)
{
if (!win.parent || win == win.parent || !(win.parent instanceof Window) )
return win;
}
return null;
}
function countFrames(frame)
{
var frameCount = 0;
try
{
for (; frame; frame = frame.callingFrame)
++frameCount;
}
catch(exc)
{
}
return frameCount;
}
function testBreakpoint(frame, bp)
{
if ( bp.condition && bp.condition != "" )
{
var result = {};
frame.scope.refresh();
if (frame.eval(bp.condition, "", 1, result))
{
if (bp.onTrue)
{
if (!result.value.booleanValue)
return false;
} else
{
var value = unwrapIValue(result.value);
if (typeof bp.lastValue == "undefined")
{
bp.lastValue = value;
return false;
} else
{
if (bp.lastValue == value)
return false;
bp.lastValue = value;
}
}
}
}
++bp.hit;
if ( bp.hitCount > 0 )
{
if ( bp.hit < bp.hitCount )
return false;
}
return true;
}
function remove(list, item)
{
var index = list.indexOf(item);
if (index != -1)
list.splice(index, 1);
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
var FirebugPrefsObserver =
{
syncFilter: function()
{
var filter = fbs.scriptsFilter;
fbs.showEvents = (filter == "all" || filter == "events");
fbs.showEvals = (filter == "all" || filter == "evals");
}
};
var QuitApplicationGrantedObserver =
{
observe: function(subject, topic, data)
{
}
};
var QuitApplicationRequestedObserver =
{
observe: function(subject, topic, data)
{
}
};
var QuitApplicationObserver =
{
observe: function(subject, topic, data)
{
fbs.disableDebugger();
fbs.shutdown();
fbs = null;
}
};
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
function unwrapIValue(object)
{
var unwrapped = object.getWrappedValue();
try
{
if (unwrapped)
return XPCSafeJSObjectWrapper(unwrapped);
}
catch (exc)
{
}
}
//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
var consoleService = null;
function ERROR(text)
{
FBTrace.sysout(text);
if (!consoleService)
consoleService = ConsoleService.getService(nsIConsoleService);
consoleService.logStringMessage(text + "");
}
function getExecutionStopNameFromType(type)
{
switch (type)
{
case jsdIExecutionHook.TYPE_INTERRUPTED: return "interrupted";
case jsdIExecutionHook.TYPE_BREAKPOINT: return "breakpoint";
case jsdIExecutionHook.TYPE_DEBUG_REQUESTED: return "debug requested";
case jsdIExecutionHook.TYPE_DEBUGGER_KEYWORD: return "debugger_keyword";
case jsdIExecutionHook.TYPE_THROW: return "interrupted";
default: return "unknown("+type+")";
}
}
// For special chromebug tracing
function getTmpFile()
{
var file = Components.classes["@mozilla.org/file/directory_service;1"].
getService(Components.interfaces.nsIProperties).
get("TmpD", Components.interfaces.nsIFile);
file.append("fbs.tmp");
file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);
FBTrace.sysout("FBS opened tmp file "+file.path);
return file;
}
function getTmpStream(file)
{
// file is nsIFile, data is a string
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
createInstance(Components.interfaces.nsIFileOutputStream);
// use 0x02 | 0x10 to open file for appending.
foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
// write, create, truncate
// In a c file operation, we have no need to set file mode with or operation,
// directly using "r" or "w" usually.
return foStream;
}
var trackFiles = {
allFiles: {},
add: function(fileName)
{
var name = new String(fileName);
this.allFiles[name] = [];
},
drop: function(fileName)
{
var name = new String(fileName);
this.allFiles[name].push("dropped");
},
def: function(frame)
{
var jscontext = frame.executionContext;
if (jscontext)
frameGlobal = new XPCNativeWrapper(jscontext.globalObject.getWrappedValue());
var scopeName = fbs.getLocationSafe(frameGlobal);
if (!scopeName)
scopeName = "noGlobalObjectLocationInJSContext:"+(jscontext?jscontext.tag:"none");
var name = new String(frame.script.fileName);
if (! (name in this.allFiles))
this.allFiles[name]=["not added"];
this.allFiles[name].push(scopeName);
},
merge: function(moreFiles)
{
for (var p in moreFiles)
{
if (p in this.allFiles)
this.allFiles[p] = this.allFiles[p].concat(moreFiles[p]);
else
this.allFiles[p] = moreFiles[p];
}
},
dump: function()
{
var n = 0;
for (var p in this.allFiles)
{
tmpout( (++n) + ") "+p);
var where = this.allFiles[p];
if (where.length > 0)
{
for (var i = 0; i < where.length; i++)
{
tmpout(", "+where[i]);
}
tmpout("\n");
}
else
tmpout(" none\n");
}
},
}
function tmpout(text)
{
if (!fbs.foStream)
fbs.foStream = getTmpStream(getTmpFile());
fbs.foStream.write(text, text.length);
}